# ~*~ coding: utf-8 ~*~
import re
import time

from apps.common.dbbase import BaseModel
from apps.common.utils import generate_uuid
from apps.extensions import db
from . import AssetNode, Asset

__all__ = ['Node']


class FamilyMixin:
    __parents = None
    __children = None
    __all_children = None
    is_node = True

    @staticmethod
    def clean_children_keys(nodes_keys):
        nodes_keys = sorted(list(nodes_keys), key=lambda x: (len(x), x))
        nodes_keys_clean = []
        for key in nodes_keys[::-1]:
            found = False
            for k in nodes_keys:
                if key.startswith(k + ':'):
                    found = True
                    break
            if not found:
                nodes_keys_clean.append(key)
        return nodes_keys_clean

    @classmethod
    def get_node_all_children_key_pattern(cls, key, with_self=True):
        pattern = r'^{0}:'.format(key)
        if with_self:
            pattern += r'|^{0}$'.format(key)
        return pattern

    @classmethod
    def get_node_children_key_pattern(cls, key, with_self=True):
        pattern = r'^{0}:[0-9]+$'.format(key)
        if with_self:
            pattern += r'|^{0}$'.format(key)
        return pattern

    def get_children_key_pattern(self, with_self=False):
        return self.get_node_children_key_pattern(self.key, with_self=with_self)

    def get_all_children_pattern(self, with_self=False):
        return self.get_node_all_children_key_pattern(self.key, with_self=with_self)

    def is_children(self, other):
        children_pattern = other.get_children_key_pattern(with_self=False)
        return re.match(children_pattern, self.key)

    def get_children(self, with_self=False):
        pattern = self.get_children_key_pattern(with_self=with_self)
        return Node.query.filter(Node.key.op('regexp')(pattern)).all()

    def get_all_children(self, with_self=False):
        pattern = self.get_all_children_pattern(with_self=with_self)
        return Node.query.filter(Node.key.op('regexp')(pattern)).all()

    @property
    def children(self):
        return self.get_children(with_self=False)

    @property
    def all_children(self):
        return self.get_all_children(with_self=False)

    def create_child(self, value):
        child_key = self.get_next_child_key()
        child = self.create(key=child_key, value=value, commit=False)
        db.session.commit()
        return child

    def get_next_child_key(self):
        mark = self.child_mark
        self.child_mark += 1
        self.save(commit=False)
        return "{}:{}".format(self.key, mark)

    def get_next_child_preset_name(self):
        name = "New node"
        values = [
            child.value[child.value.rfind(' '):]
            for child in self.get_children()
            if child.value.startswith(name)
        ]
        values = [int(value) for value in values if value.strip().isdigit()]
        count = max(values) + 1 if values else 1
        return '{} {}'.format(name, count)

    # Parents
    @classmethod
    def get_node_ancestor_keys(cls, key, with_self=False):
        parent_keys = []
        key_list = key.split(":")
        if not with_self:
            key_list.pop()
        for i in range(len(key_list)):
            parent_keys.append(":".join(key_list))
            key_list.pop()
        return parent_keys

    def get_ancestor_keys(self, with_self=False):
        return self.get_node_ancestor_keys(
            self.key, with_self=with_self
        )

    @property
    def ancestors(self):
        return self.get_ancestors(with_self=False)

    def get_ancestors(self, with_self=False):
        ancestor_keys = self.get_ancestor_keys(with_self=with_self)
        return Node.query.filter(Node.key.in_(ancestor_keys)).all()

    @property
    def parent_key(self):
        parent_key = ":".join(self.key.split(":")[:-1])
        return parent_key

    def is_parent(self, other):
        return other.is_children(self)

    @property
    def parent(self):
        parent_key = self.parent_key
        return Node.query.filter(Node.key == parent_key).first()

    @parent.setter
    def parent(self, parent):
        """
        设置当前节点的父节点
        :param parent: self.parent = parent
            self_key    -->    '2:1'
            parent_key  -->    '1'
        :return:
        """
        if not self.is_node:
            self.key = parent.key + ':fake'
            return
        children = self.get_all_children()  # ['2:1:0','2:1:1']
        old_key = self.key  # '2:1'
        self.key = parent.get_next_child_key()  # '1:3'
        self.save(commit=False)
        for child in children:
            child.key = child.key.replace(old_key, self.key, 1)  # '1:3:0' = '2:1:0'.replace('2:1', '1:3', 1)
            child.save(commit=False)
        db.session.commit()

    def get_siblings(self, with_self=False):
        key = ':'.join(self.key.split(':')[:-1])
        pattern = r'^{}:[0-9]+$'.format(key)
        sibling = Node.query.filter(Node.key.op('regexp')(pattern)).all()
        if not with_self:
            sibling = Node.query.filter(Node.key.op('regexp')(pattern), Node.key != self.key).all()
        return sibling

    def get_family(self):
        ancestors = self.get_ancestors()
        children = self.get_all_children()
        return [*tuple(ancestors), self, *tuple(children)]


class AssetMixin:

    def assets_amount(self):
        if self.is_root:
            count = db.session.query(Asset.id).count()
        else:
            nodes = self.get_all_children(with_self=True)
            node_ids = [node.id for node in nodes]
            count = db.session.query(AssetNode.id).filter(AssetNode.node_id.in_(node_ids)).count()
        return count

    def get_assets(self):
        nodes = self.get_all_children(with_self=True)
        node_ids = [node.id for node in nodes]
        assets = db.session.query(Asset).\
            join(AssetNode, Asset.id == AssetNode.asset_id).\
            filter(AssetNode.node_id.in_(node_ids)).\
            all()
        return assets


class Node(FamilyMixin, AssetMixin, BaseModel):
    """
    树形节点菜单
    """
    __tablename__ = 'assets_node'

    id = db.Column(db.CHAR(36), default=generate_uuid, primary_key=True)
    key = db.Column(db.VARCHAR(128), unique=True)  # '1:1:1:1'
    value = db.Column(db.VARCHAR(128))
    child_mark = db.Column(db.INT, default=0)

    create_by = db.Column(db.VARCHAR(32), nullable=True)
    create_time = db.Column(db.INT, default=time.time)
    update_time = db.Column(db.INT, default=time.time, onupdate=time.time)

    is_node = True

    def __str__(self):
        return self.value

    def __eq__(self, other):
        if not other:
            return False
        return self.id == other.id

    def __gt__(self, other):
        self_key = [int(k) for k in self.key.split(':')]
        other_key = [int(k) for k in other.key.split(':')]
        self_parent_key = self_key[:-1]
        other_parent_key = other_key[:-1]

        if self_parent_key and self_parent_key == other_parent_key:
            return self.value > other.value
        return self_key > other_key

    def __lt__(self, other):
        return not self.__gt__(other)

    @property
    def name(self):
        return self.value

    @property
    def level(self):
        return len(self.key.split(':'))

    @property
    def is_root(self):
        if self.key.isdigit():
            return True
        else:
            return False

    @classmethod
    def root(cls):
        return Node.query.filter(Node.key == '1').first()

    def delete(self, commit=True):
        self.parent.child_mark -= 1
        self.parent.save(commit=False)
        return super().delete(commit=commit)

    def as_tree_node(self):
        name = '{} ({})'.format(self.value, self.assets_amount())
        tree_node = {
            'id': self.key,
            'name': name,
            'title': name,
            'pId': self.parent_key,
            'isParent': True,
            'open': self.is_root,
            "iconSkin": "",
            'meta': {
                'node': {
                    "id": self.id,
                    "key": self.key,
                    "value": self.value,
                    "name": self.name,
                    "assets_amount": self.assets_amount(),
                },
                'type': 'node'
            }
        }
        return tree_node

    def as_tree_node2(self):
        def gen_nodes(parent, children, tree):
            for child in children:
                node = {
                    'id': child.id,
                    'pid': parent.id,
                    'text': child.value,
                    'nodes': []
                }
                nodes = tree.get('nodes')
                nodes.append(node)

                if child.children:
                    gen_nodes(child, child.children, node)
                # else:
                #     node.pop('nodes')

        tree_node = {
            'id': self.id,
            'pid': "",
            'text': self.value,
            'nodes': []
        }
        gen_nodes(self, self.children, tree_node)
        return [tree_node]
